/*******************************************************************************
 * Copyright (c) 2014 - 2016 Ericsson and others.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *   Marc Khouzam (Ericsson) - Initial API and implementation
 *******************************************************************************/

package org.eclipse.cdt.dsf.gdb.service;

import java.util.Hashtable;
import java.util.Map;

import org.eclipse.cdt.dsf.concurrent.ConfinedToDsfExecutor;
import org.eclipse.cdt.dsf.concurrent.DataRequestMonitor;
import org.eclipse.cdt.dsf.concurrent.ImmediateRequestMonitor;
import org.eclipse.cdt.dsf.concurrent.RequestMonitor;
import org.eclipse.cdt.dsf.concurrent.Sequence.Step;
import org.eclipse.cdt.dsf.datamodel.IDMContext;
import org.eclipse.cdt.dsf.debug.service.IBreakpoints;
import org.eclipse.cdt.dsf.debug.service.IBreakpointsExtension;
import org.eclipse.cdt.dsf.gdb.internal.GdbPlugin;
import org.eclipse.cdt.dsf.mi.service.IMICommandControl;
import org.eclipse.cdt.dsf.mi.service.IMIRunControl;
import org.eclipse.cdt.dsf.mi.service.MIBreakpointDMData;
import org.eclipse.cdt.dsf.mi.service.MIBreakpoints;
import org.eclipse.cdt.dsf.mi.service.MIBreakpointsSynchronizer;
import org.eclipse.cdt.dsf.mi.service.command.CommandFactory;
import org.eclipse.cdt.dsf.mi.service.command.output.MIBreakInsertInfo;
import org.eclipse.cdt.dsf.mi.service.command.output.MIBreakpoint;
import org.eclipse.cdt.dsf.service.DsfSession;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;

/**
 * Breakpoints service for GDB 7.7.
 * This version supports dynamic printf
 * 
 * @since 4.4
 */
public class GDBBreakpoints_7_7 extends GDBBreakpoints_7_6 {
	
	private IMICommandControl fConnection;
	private IMIRunControl fRunControl;
	private CommandFactory fCommandFactory;

	public GDBBreakpoints_7_7(DsfSession session) {
		super(session);
	}

	@Override
	public void initialize(final RequestMonitor rm) {
		super.initialize(new ImmediateRequestMonitor(rm) {
			@Override
			protected void handleSuccess() {
				doInitialize(rm);
			}
		});
	}

	private void doInitialize(final RequestMonitor rm) {
		fConnection = getServicesTracker().getService(IMICommandControl.class);
		fRunControl = getServicesTracker().getService(IMIRunControl.class);
		if (fConnection == null || fRunControl == null) {
			rm.done(new Status(IStatus.ERROR, GdbPlugin.PLUGIN_ID, "Service is not available")); //$NON-NLS-1$
			return;
		}
		
		fCommandFactory = fConnection.getCommandFactory();
		
		// Register this service
		register(new String[] { IBreakpoints.class.getName(),
		                        IBreakpointsExtension.class.getName(),
								MIBreakpoints.class.getName(),
								GDBBreakpoints_7_0.class.getName(),
								GDBBreakpoints_7_2.class.getName(),
								GDBBreakpoints_7_4.class.getName(),
								GDBBreakpoints_7_6.class.getName(),
								GDBBreakpoints_7_7.class.getName() },
				new Hashtable<String, String>());

		rm.done();
	}

	@Override
	public void shutdown(RequestMonitor requestMonitor) {
        unregister();
		super.shutdown(requestMonitor);
	}

	@Override
	protected void addDynamicPrintf(
			final IBreakpointsTargetDMContext context, 
			final Map<String, Object> attributes, 
			final DataRequestMonitor<IBreakpointDMContext> drm) {
		final MIBreakpointsSynchronizer bs = getServicesTracker().getService(MIBreakpointsSynchronizer.class);
		if (bs != null) {
			// Skip the dprintf set from the console or from outside of Eclipse 
			// because they are already installed on the target.
			bs.getTargetBreakpoint(
					context, 
					attributes, 
					new DataRequestMonitor<MIBreakpoint>(getExecutor(), drm) {
						@Override
						@ConfinedToDsfExecutor( "fExecutor" )
						protected void handleSuccess() {
							MIBreakpoint miBpt = getData();
							if (miBpt != null) {
								bs.removeCreatedTargetBreakpoint(context, miBpt);
								MIBreakpointDMData newBreakpoint = createMIBreakpointDMData(miBpt);
								getBreakpointMap(context).put(newBreakpoint.getNumber(), newBreakpoint);
								IBreakpointDMContext dmc = 
									new MIBreakpointDMContext(GDBBreakpoints_7_7.this, new IDMContext[] { context }, newBreakpoint.getNumber());
								drm.setData(dmc);
								getSession().dispatchEvent(new BreakpointAddedEvent(dmc), getProperties());
								drm.done();
							}
							else {
								doAddDynamicPrintf(context, attributes, drm);
							}
						}
					});
		}
		else {
			doAddDynamicPrintf(context, attributes, drm);
		}
	}

	/**
	 * Add a Dynamic Printf.
	 */
	protected void doAddDynamicPrintf(final IBreakpointsTargetDMContext context, Map<String, Object> attributes, final DataRequestMonitor<IBreakpointDMContext> finalRm)
	{
		// Select the context breakpoints map
		final Map<String, MIBreakpointDMData> contextBreakpoints = getBreakpointMap(context);
		if (contextBreakpoints == null) {
       		finalRm.done(new Status(IStatus.ERROR, GdbPlugin.PLUGIN_ID, REQUEST_FAILED, UNKNOWN_BREAKPOINT_CONTEXT, null));
			return;
		}

		// Extract the relevant parameters (providing default values to avoid potential NPEs)
		final String location = formatLocation(attributes);
		if (location.equals(NULL_STRING)) {
       		finalRm.done(new Status(IStatus.ERROR, GdbPlugin.PLUGIN_ID, REQUEST_FAILED, UNKNOWN_BREAKPOINT_CONTEXT, null));
			return;
		}

		final String printfStr       = (String)  getProperty(attributes, MIBreakpoints.PRINTF_STRING,     ""); //$NON-NLS-1$
		final Boolean enabled        = (Boolean) getProperty(attributes, MIBreakpoints.IS_ENABLED,        true);
		final Boolean isTemporary    = (Boolean) getProperty(attributes, MIBreakpointDMData.IS_TEMPORARY, false);
		final String  condition      = (String)  getProperty(attributes, MIBreakpoints.CONDITION,         NULL_STRING);
		final Integer ignoreCount    = (Integer) getProperty(attributes, MIBreakpoints.IGNORE_COUNT,      0);
		String  threadId             = (String)  getProperty(attributes, MIBreakpointDMData.THREAD_ID,    "0"); //$NON-NLS-1$
		final int tid                = Integer.parseInt(threadId);

	    final Step insertDPrintf = new Step() {
    		@Override
    		public void execute(final RequestMonitor rm) {
    			// Execute the command
    			fConnection.queueCommand(
    					fCommandFactory.createMIDPrintfInsert(context, isTemporary, condition, ignoreCount, tid, !enabled, location, printfStr),
    					new DataRequestMonitor<MIBreakInsertInfo>(getExecutor(), rm) {
    						@Override
    						protected void handleSuccess() {

    							// With MI, an invalid location won't generate an error
    							if (getData().getMIBreakpoints().length == 0) {
    								rm.done(new Status(IStatus.ERROR, GdbPlugin.PLUGIN_ID, REQUEST_FAILED, DYNAMIC_PRINTF_INSERTION_FAILURE, null));
    								return;
    							}

    							// Create a breakpoint object and store it in the map
    							final MIBreakpointDMData newBreakpoint = createMIBreakpointDMData(getData().getMIBreakpoints()[0]);
    							String reference = newBreakpoint.getNumber();
    							if (reference.isEmpty()) {
    								rm.done(new Status(IStatus.ERROR, GdbPlugin.PLUGIN_ID, REQUEST_FAILED, DYNAMIC_PRINTF_INSERTION_FAILURE, null));
    								return;
    							}
    							contextBreakpoints.put(reference, newBreakpoint);

    							// Format the return value
    							MIBreakpointDMContext dmc = new MIBreakpointDMContext(GDBBreakpoints_7_7.this, new IDMContext[] { context }, reference);
    							finalRm.setData(dmc);

    							// Flag the event
    							getSession().dispatchEvent(new BreakpointAddedEvent(dmc), getProperties());

    							rm.done();
    						}

    						@Override
    						protected void handleError() {
    							rm.done(new Status(IStatus.ERROR, GdbPlugin.PLUGIN_ID, REQUEST_FAILED, DYNAMIC_PRINTF_INSERTION_FAILURE, getStatus().getException()));
    						}
    					});
    		}
	    };
	    
	    fRunControl.executeWithTargetAvailable(context, new Step[] { insertDPrintf }, finalRm);
	}
}
